#!/usr/bin/env python
# -*- coding: utf-8 -*-
import time
import traceback

from fastapi import FastAPI, Request, Response
from fastapi.exceptions import RequestValidationError, ValidationError
from starlette.exceptions import HTTPException
from starlette.middleware.base import RequestResponseEndpoint

from .middlewares import RequestContextLogMiddleware
from ..common.api_response import (
    ResponseEnum,
    BusinessException,
    ApiResponse,
)
from ..common.router import api_router
from ..file_logger import logger
from ..settings import settings


def create_app() -> FastAPI:
    """
    生成FatAPI对象
    :return:
    """
    app = FastAPI(
        debug=settings.DEBUG,
        title=settings.TITLE,
        description=settings.DESCRIPTION,
        docs_url=settings.DOCS_URL,
        openapi_url=settings.OPENAPI_URL,
        redoc_url=settings.REDOC_URL,
    )

    # 注册捕获全局异常
    register_exception(app)

    # 请求拦截
    register_hook(app)
    # 注册路由
    register_router(app)

    return app


def register_exception(app: FastAPI) -> None:
    """
    全局异常捕获
    注意 别手误多敲一个s
    exception_handler
    exception_handlers
    两者有区别
        如果只捕获一个异常 启动会报错
        @exception_handlers(UserNotFound)
    TypeError: 'dict' object is not callable
    :param app:
    :return:
    """

    @app.exception_handler(BusinessException)
    async def business_exception_handler(
        request: Request, exception: BusinessException
    ):
        logger.error(
            {
                "op": "LogBusinessException",
                "error": exception.response_message_enum,
                "traceback": traceback.format_exc(),
            }
        )
        return ApiResponse.build_error(response_enum=exception.response_message_enum)

    @app.exception_handler(ValidationError)
    async def inner_validation_exception_handler(
        request: Request, exception: ValidationError
    ):
        """
        内部参数验证异常
        :param request:
        :param exception:
        :return:
        """
        logger.error(
            {
                "op": "LogValidationError",
                "error": str(exception),
                "traceback": traceback.format_exc(),
            }
        )
        return ApiResponse.build_error(response_enum=ResponseEnum.METHOD_ARGUMENT_ERROR)

    @app.exception_handler(RequestValidationError)
    async def request_validation_exception_handler(
        request: Request, exception: RequestValidationError
    ):
        """
        请求参数验证异常
        :param request:
        :param exception:
        :return:
        """
        logger.error(
            {
                "op": "LogRequestValidationError",
                "error": str(exception),
                "traceback": traceback.format_exc(),
            }
        )
        return ApiResponse.build_error(response_enum=ResponseEnum.METHOD_ARGUMENT_ERROR)

    # http相关的异常
    @app.exception_handler(HTTPException)
    async def http_exception_handler(request: Request, exception: HTTPException):
        logger.error(
            {
                "op": "LogHTTPException",
                "error": str(exception),
                "traceback": traceback.format_exc(),
            }
        )
        return ApiResponse.build_error(response_enum=ResponseEnum.SYS_ERROR)

    # 捕获全部异常
    @app.exception_handler(Exception)
    async def all_exception_handler(request: Request, exception: Exception):
        """
        全局所有异常
        :param request:
        :param exception:
        :return:
        """
        logger.error(
            {
                "op": "LogException",
                "error": str(exception),
                "traceback": traceback.format_exc(),
            }
        )
        return ApiResponse.build_error(response_enum=ResponseEnum.SYS_ERROR)


def register_hook(app: FastAPI) -> None:
    """
    请求响应拦截 hook
    PS1：从上面的输出结果可以看得到我们的中间件的注册顺序非常的重要。他们的上面的注册顺序是：
    ---->越是最晚注册的，它就会在中间件的洋葱模型的最外层。
    ---->越是最早注册的，它就会在中间件的洋葱模型的最内层。
    PS2：假如你有需要使用中间件来处理全局异常的捕获的话，则当然是放在最外层去处理咯！

    链接：https://juejin.cn/post/6971451349141553165

    :param app:
    :return:
    """

    @app.middleware("http")
    async def logger_request(
        request: Request, call_next: RequestResponseEndpoint
    ) -> Response:
        # https://stackoverflow.com/questions/60778279/fastapi-middleware-peeking-into-responses
        # https://juejin.cn/post/6971451349141553165
        start_time = time.time()
        try:
            response = await call_next(request)
            duration = (time.time() - start_time) * 1000
            logger.info(
                {
                    "op": "RecordRequestAndResponse",
                    "duration(ms)": f"{duration:.3f}",
                }
            )
            return response
        except Exception as error:
            # https://stackoverflow.com/questions/61596911/catch-exception-in-fast-api-globally
            # 全局Exception异常无法在debug==True的时候通过app.exception_handler捕获
            logger.error(
                {
                    "op": "LogException",
                    "error": str(error),
                    "traceback": traceback.format_exc(),
                }
            )
            return ApiResponse.build_error(response_enum=ResponseEnum.SYS_ERROR)

    app.add_middleware(RequestContextLogMiddleware)


def register_router(app: FastAPI) -> None:
    """
    注册路由
    :param app:
    :return:
    """
    # 项目API
    app.include_router(
        api_router,
    )
